package com.fa4j.common.base.util;

import com.fa4j.common.base.annotation.Module;
import com.fa4j.common.base.exception.ErrorType;
import com.fa4j.common.base.exception.ServerException;
import com.fa4j.common.base.model.BaseEnum;
import com.fa4j.common.base.model.ModuleInfo;
import lombok.SneakyThrows;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class ModuleUtil {

    private static final Map<String, ModuleInfo> MODULE_MAP = new ConcurrentHashMap<>();

    public static ModuleInfo getModule(Class<?> clazz) {
        String packageName = clazz.getPackageName();
        List<ModuleInfo> list = queryModuleList(packageName, 1);
        if (list.size() == 1) {
            return list.get(0);
        }
        throw new ServerException(clazz.getName() + "未设置模块信息,请在对应包下面设置package-info！");
    }

    public static List<ModuleInfo> queryModuleList(String packageName, Integer maxNum) {
        List<ModuleInfo> result = new ArrayList<>();
        while (!packageName.isEmpty() && result.size() < maxNum) {
            if (MODULE_MAP.containsKey(packageName)) {
                result.add(MODULE_MAP.get(packageName));
            } else {
                Module module = null;
                String version = null;
                try {
                    Class<?> pkgInfoCLass = Class.forName(packageName + ".package-info");
                    module = pkgInfoCLass.getAnnotation(Module.class);
                    version = pkgInfoCLass.getPackage().getImplementationVersion();
                } catch (Exception ignored) {
                }
                if (module != null) {
                    if (module.code() == 0) {
                        throw new ServerException("模块需要设置模块编码,请在对应包" + packageName + ".package-info.java中设置");
                    }
                    ModuleInfo info = new ModuleInfo();
                    info.setType(module.type());
                    info.setName(module.name());
                    info.setCode(module.code());
                    info.setDescription(module.description());
                    info.setPackageName(packageName);
                    info.setPackageVersion(version);
                    setEnumAndErrorType(info);
                    result.add(info);
                    MODULE_MAP.put(packageName, info);
                }
            }
            packageName = packageName.contains(".")
                    ? packageName.substring(0, packageName.lastIndexOf('.'))
                    : "";
        }
        return result;
    }


    @SneakyThrows
    private static void setEnumAndErrorType(ModuleInfo info) {
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        MetadataReaderFactory metaReader = new CachingMetadataReaderFactory(resourceLoader);
        String locationPattern = "classpath*:" + info.getPackageName().replace(".", "/") + "/model/*.class";
        Resource[] resources = resolver.getResources(locationPattern);
        for (Resource r : resources) {
            MetadataReader reader = metaReader.getMetadataReader(r);
            ClassMetadata classMetadata = reader.getClassMetadata();
            if (Objects.equals(classMetadata.getSuperClassName(), Enum.class.getName())
                    && Arrays.stream(classMetadata.getInterfaceNames()).anyMatch(name ->
                    BaseEnum.class.getName().equals(name) || ErrorType.class.getName().equals(name)
            )) {
                Class<?> clazz = ClassUtils.forName(reader.getClassMetadata().getClassName(), ClassUtils.getDefaultClassLoader());
                if (BaseEnum.class.isAssignableFrom(clazz)) {
                    info.getEnumClassList().add(clazz.asSubclass(BaseEnum.class));
                }
                if (ErrorType.class.isAssignableFrom(clazz)) {
                    info.getErrorClassList().add(clazz.asSubclass(ErrorType.class));
                }
            }
        }
    }


}
